/**
* This file handles all storage/database related functions
*/

//-------------------------------------------------------
// Header Files
//-------------------------------------------------------
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include "storage.h"
#include "utils.h"
#include "shared.h" // For logger(), message

//-------------------------------------------------------
// Constants
//-------------------------------------------------------
extern FILE *fileClient; // file to be used for logger() in client.c & storage.c

//-------------------------------------------------------
// Storage Functions
//-------------------------------------------------------
/**
* This function connects to a port with hostname
* @param hostname localhost
* @param port A port number
*/
// @note: Always use {"localhost", 1376)
void* storage_connect(const char *hostname, const int port)
{
    // Create a socket.
    // @note: Both server and clients must create socket before it can connect to it
    int sockfd = socket(PF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
	return NULL;
    // Get info about the server.
    struct addrinfo serveraddr, *res;
    // Initialize
    memset(&serveraddr, 0, sizeof serveraddr);
    serveraddr.ai_family = AF_UNSPEC; // Ipv4 or IPv6
    serveraddr.ai_socktype = SOCK_STREAM;
    char portstr[MAX_PORT_LEN];
    // write onto portstr MAX_PORT_LEN for the value port given
    // Basically converts the int port value given into a string
    snprintf(portstr, sizeof portstr, "%d", port);
		//e.g. n = (buffer, 10, "haha what %d you %d, 2, 3)
    // Gets address info of "localhost", "1376", hint of type of address structure, list of socket address structures found)
    int status = getaddrinfo(hostname, portstr, &serveraddr, &res); // returns 0 if succeeds, or one of error codes if fails
    if (status != 0)
	return NULL;
    // Connect to socket referred by sockfd and address referred to be res->ai_addr which has size res->ai_addrlen
    status = connect(sockfd, res->ai_addr, res->ai_addrlen); // returns 0 if successful and -1 if fails
    if (status != 0)
	return NULL;
    // JUSTIFY 1: A logger call here to know connection is successful to right port, we know it is successful here
    //		  cause getaddrinfo and connect both returns a 1 to status
    sprintf(message, "Connection in storage.c is successful with port %s\n", portstr);
    logger(fileClient, message);
    // Return a pointer to the socket file descriptor
    return (void*) sockfd;
}

/**
* This function autheticates a connection to socket
* and prints to socketfd what has been authenticated
*/
int storage_auth(const char *username, const char *passwd, void *conn)
{
    // Connection is really just a socket file descriptor.
    int sockfd = (int)conn;
    // Send some data.
    // Create buffer
    char buf[MAX_CMD_LEN];
    memset(buf, 0, sizeof buf);
    // Generate encrypted password
    char *encrypted_passwd = generate_encrypted_password(passwd, NULL);
    // Write to buffer "Authenticated username and encrypted password"
    snprintf(buf, sizeof buf, "AUTH %s %s\n", username, encrypted_passwd);
    // Send buffers and receive buffers
    if (sendall(sockfd, buf, strlen(buf)) == 0 && recvline(sockfd, buf, sizeof buf) == 0)
    {
	// @note: sendall() makes sure it sends all strlen(buf) before it finishes
		// else, it will return -1 and wont go to recvline
	// Return 0 if successful for both send buffer to sockfd and recv from sockfd to buffer
    	// JUSTIFY 2: A logger call here to know if the data and sends have been sent & received properly 
    	sprintf(message, "Both sendall and receive all in storage_auth successful\n");
    	logger(fileClient, message);
	return 0;
    }
    return -1;
}

/**
* This function is used to get a record given a key and table
*/
// TODO: Implement this function properly in future milestones
// @note: This function currently just print stuff except that it also modifies r.value 
//	  as it copies buffer into r.value using strncpy()
int storage_get(const char *table, const char *key, struct storage_record *record, void *conn)
{
    // Connection is really just a socket file descriptor.
    int sockfd = (int)conn;
    // Send some data.
    char buf[MAX_CMD_LEN];
    memset(buf, 0, sizeof buf);
    snprintf(buf, sizeof buf, "GET %s %s\n", table, key);
    if (sendall(sockfd, buf, strlen(buf)) == 0 && recvline(sockfd, buf, sizeof buf) == 0)
    {
	// Note: Copies buffer to record->value
	strncpy(record->value, buf, sizeof record->value);
    // JUSTIFY 3: A logger call here to know that buffer has been copied to record value successfully
    sprintf(message, "Copied buffer to record value is %s\n", record->value);
    logger(fileClient, message);
	return 0;
    }
    return -1;
}

/**
* This function sets a record given a key and table
*/
// TODO: Implement this function properly in future milestones
int storage_set(const char *table, const char *key, struct storage_record *record, void *conn)
{
    // Connection is a socket file descriptor.
    int sockfd = (int)conn;
    // Initialize buffer
    char buf[MAX_CMD_LEN];
    memset(buf, 0, sizeof buf);
    // Copy str to buffer indicating has been set
    snprintf(buf, sizeof buf, "SET %s %s %s\n", table, key, record->value);
    // send buffer to sockfd and receive anything in sockfd
    if (sendall(sockfd, buf, strlen(buf)) == 0 && recvline(sockfd, buf, sizeof buf) == 0)
    {
	return 0;
    }
    return -1;
}

/**
* This function disconnects a given sockfd
* @param conn The sockfd to be disconnected
*/
int storage_disconnect(void *conn)
{
    // Cleanup
    int sockfd = (int)conn;
    close(sockfd);
    // JUSTIFY 4: A logger call here to know disconnect is successful
    sprintf(message, "Disconnection in storage.c is successful\n");
    logger(fileClient, message);
    return 0;
}
